package cn.parser;

import cn.dao.NeoDao;
import cn.relation.RelationEntity;
import cn.structure.AbstractInvocationEntity;
import cn.structure.BaseEntity;
import cn.structure.ClassEntity;
import cn.structure.ConstructorCallEntity;
import cn.structure.InvocationEntity;
import cn.structure.MethodEntity;
import cn.structure.VariableEntity;
import cn.structure.edp.AbstractInterface;
import cn.structure.edp.CreateObject;
import cn.structure.edp.EdpEntity;
import cn.structure.edp.Inheritance;
import cn.structure.edp.Retrieve;
import cn.structure.ipc.FulfillMethod;
import cn.structure.ipc.IpcEntity;
import cn.structure.ipc.RetrieveNew;
import cn.type.EntityTypeEnum;
import cn.type.RelTypeEnum;
import cn.utils.SpoonUtil;

import java.util.HashSet;
import java.util.Set;

public class IpcParser {
    private static IpcParser ipcParser = new IpcParser();
    private static EntityFactory factory = EntityFactoryImpl.getInstance();
    private static EdpParser edpParser = EdpParser.getInstance();
    private static Set<IpcEntity> ipc_set = new HashSet<>();

    public static IpcParser getInstance() {
        return ipcParser;
    }

    public void createIpcRelation(BaseEntity ipc, BaseEntity target, Enum<?> role) {
        RelationEntity relationEntity = factory.createRelationEntity(ipc, target, RelTypeEnum.HAS_ROLE, EntityTypeEnum.IpcEntity);
        factory.addRelationProperty(relationEntity, "role", role.toString());
    }

    public void parseFulfillMethod() {
        edpParser.getEdpEntities().stream()
                .filter(edpEntity -> edpEntity instanceof AbstractInterface)
                .forEach(abstractInterface -> {
                    ClassEntity abstractor = ((AbstractInterface) abstractInterface).getAbstractor();
                    MethodEntity operation = ((AbstractInterface) abstractInterface).getOperation();
                    edpParser.getEdpEntities().stream()
                            .filter(edpEntity -> edpEntity instanceof Inheritance)
                            .forEach(inheritance -> {
                                ClassEntity superClass = ((Inheritance) inheritance).getSuperClass();
                                ClassEntity subClass = ((Inheritance) inheritance).getSubClass();
                                if (abstractor.equals(superClass)) {
                                    subClass.getMethods().forEach(ctMethod -> {
                                        if (ctMethod.getSignature().equals(operation.getSignature())) {
                                            FulfillMethod fulfillMethod = new FulfillMethod((AbstractInterface) abstractInterface, (Inheritance) inheritance);
                                            ipc_set.add(fulfillMethod);
                                            createIpcRelation(fulfillMethod, abstractInterface, FulfillMethod.Role.abstractInterface);
                                            createIpcRelation(fulfillMethod, inheritance, FulfillMethod.Role.inheritance);
                                        }
                                    });
                                }
                            });
                });
    }

    public void parseRetrieveNew() {
        edpParser.getEdpEntities().stream()
                .filter(edpEntity -> edpEntity instanceof Retrieve)
                .forEach(retrieve -> {
                    BaseEntity source = ((Retrieve) retrieve).getSource();
                    ClassEntity sourceAccess = ((Retrieve) retrieve).getSourceAccess();
                    ClassEntity retrieved = ((Retrieve) retrieve).getRetrieved();
                    edpParser.getEdpEntities().stream()
                            .filter(edpEntity -> edpEntity instanceof CreateObject)
                            .forEach(createObject -> {
                                BaseEntity creationSite = ((CreateObject) createObject).getCreationSite();
                                ClassEntity typeToCreate = ((CreateObject) createObject).getTypeToCreate();
                                ConstructorCallEntity objectCreated = ((CreateObject) createObject).getObjectCreated();
                                boolean flag = false;
                                if (source.equals(createObject)) {
                                    flag = true;
                                } else if (source instanceof VariableEntity && creationSite instanceof MethodEntity) {
                                    for (BaseEntity incoming : SpoonUtil.getIncomings(creationSite, RelTypeEnum.CALLED)) {
                                        if (source.getAst_path().equals(((InvocationEntity) incoming).getCaller_obj().getPath().toString())
                                                && ((MethodEntity) creationSite).getDeclaration_class().getQualifiedName().equals(((VariableEntity) source).getOrigin().getQualifiedName())) {
                                            flag = true;
                                            break;
                                        }
                                    }
                                } else if (source instanceof MethodEntity && creationSite instanceof VariableEntity) {
                                    for (BaseEntity outgoing : SpoonUtil.getOutgoings(creationSite, RelTypeEnum.HAS_INVOKE)) {
                                        if (source.getAst_path().equals(((AbstractInvocationEntity) outgoing).getCalled_executable().getExecutableDeclaration().getPath().toString())) {
                                            flag = true;
                                            break;
                                        }
                                    }
                                } else if (source instanceof VariableEntity && creationSite instanceof VariableEntity) {
                                    for (BaseEntity outgoing : SpoonUtil.getOutgoings(creationSite, RelTypeEnum.HAS_INVOKE)) {
                                        if (source.getAst_path().equals(((AbstractInvocationEntity) outgoing).getCaller_obj().getPath().toString())) {
                                            flag = true;
                                            break;
                                        }
                                    }
                                }
                                if (flag && retrieved.equals(typeToCreate) && !objectCreated.getCalled_class().getQualifiedName().equals(sourceAccess.getName())) {
                                    RetrieveNew retrieveNew = new RetrieveNew((Retrieve) retrieve, (CreateObject) createObject);
                                    ipc_set.add(retrieveNew);
                                    createIpcRelation(retrieveNew, retrieve, RetrieveNew.Role.retrieve);
                                    createIpcRelation(retrieveNew, createObject, RetrieveNew.Role.createObject);
                                }
                            });
                });
    }

    public void parse() {
        parseFulfillMethod();
        parseRetrieveNew();

    }

    public Set<RelationEntity> getIpcRelationEntities() {
        return EntityFactory.ipc_relationship_set;
    }

    public Set<IpcEntity> getIpcEntities() {
        return ipc_set;
    }

    public long getEdpNodeId(EdpEntity entity) {
        return NeoDao.edp_node_id_map.get(entity);
    }

    public long getIpcNodeId(IpcEntity entity) {
        return NeoDao.ipc_node_id_map.get(entity);
    }
}
